package codeine;

import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import org.apache.log4j.Logger;

import codeine.configuration.ConfigurationManager;
import codeine.configuration.Links;
import codeine.configuration.PathHelper;
import codeine.configuration.directory.DiscoveredProjectJson;
import codeine.configuration.directory.PeerStatusJson;
import codeine.jsons.info.CodeineRuntimeInfo;
import codeine.jsons.nodes.NodeJson;
import codeine.jsons.nodes.NodeListJson;
import codeine.jsons.nodes.NodesManager;
import codeine.jsons.project.ProjectJson;
import codeine.model.Constants;
import codeine.mongo.IAlertsDatabaseConnector;
import codeine.utils.HttpUtils;
import codeine.utils.InetUtils;
import codeine.utils.StringUtils;
import codeine.utils.ThreadUtils;

import com.google.common.collect.Lists;
import com.google.gson.Gson;

public class PeerInDirectoryRegistrator implements Runnable {

	private static final Logger log = Logger.getLogger(PeerInDirectoryRegistrator.class);
	private static final long MAX_SLEEP_BETWEEN_REGISTRATIONS = TimeUnit.HOURS.toMillis(24);
	private static final long MIN_SLEEP_BETWEEN_REGISTRATIONS = TimeUnit.HOURS.toMillis(6);
	private static final long SLEEP_AFTER_FAILURE = TimeUnit.HOURS.toMillis(6);
	private Random random = new Random();
	private Object waitObject = new Object();

	@Inject
	private CodeineRuntimeInfo peerRuntimeInfo;
	@Inject
	private ConfigurationManager configurationManager;
	@Inject
	private Links links;
	@Inject
	private NodesManager nodesManager;
	@Inject
	private Gson gson;
	@Inject
	private IAlertsDatabaseConnector mongoConnector;
	
	@Override
	public void run() {
		execute();
	}

	private void execute() {
		boolean slept = false;
		while (true) {
			try {
				log.info("registring");
				slept = false;
				registerInDirectory();
				slept = true;
				long timeToSleep = (MIN_SLEEP_BETWEEN_REGISTRATIONS + (long)random.nextInt(Integer.MAX_VALUE)) % MAX_SLEEP_BETWEEN_REGISTRATIONS;
				String f = StringUtils.formatTimePeriod(timeToSleep);
				log.info("going to sleep " + f);
				ThreadUtils.wait(waitObject, timeToSleep);
			} catch (Exception e) {
				log.warn("fail in registration thread", e);
				if (!slept){
					ThreadUtils.wait(waitObject, SLEEP_AFTER_FAILURE);
				}
			}
		}
	}

	private void registerInDirectory() {
		List<DiscoveredProjectJson> projects = Lists.newArrayList();
		for (ProjectJson project : configurationManager.getConfiguredProjects()) {
			NodeListJson nodesOf = nodesManager.nodesOf(project);
			List<NodeJson> nodes = nodesOf == null ? Lists.<NodeJson>newArrayList() : nodesOf.nodes();
			if (!nodes.isEmpty()) {
				projects.add(new DiscoveredProjectJson(project.name(), nodes));
			}
		}
		PeerStatusJson status = new PeerStatusJson(InetUtils.getLocalHost().getHostName(),
				peerRuntimeInfo.port(),
				peerRuntimeInfo.version(),
				peerRuntimeInfo.startTime(), 
				Constants.getInstallDir(), projects, PathHelper.getTarFile(), System.currentTimeMillis());
		String directoryPeerStatus = links.directoryPeerStatus();
		String json = gson.toJson(status);
		log.info("registering in directory " + directoryPeerStatus);
		log.info("registering in directory " + json);
		HttpUtils.post(directoryPeerStatus, json);
		mongoConnector.put(status);
	}

	public void register() {
		synchronized (waitObject) {
			waitObject.notify();
		}
	}

}
